#include "clUtils.h"
#include <fstream>
#include "FormatString.h"

using namespace std;

const string errorCodeToMessage(cl_int code) {
    switch (code) {
        case CL_SUCCESS : return "Success";
        
        case CL_DEVICE_NOT_FOUND : return "Device not found";
        case CL_DEVICE_NOT_AVAILABLE : return "Device not available";
        case CL_COMPILER_NOT_AVAILABLE : return "Compiler not available";
        case CL_MEM_OBJECT_ALLOCATION_FAILURE : return "Mem object allocation failure";
        case CL_OUT_OF_RESOURCES : return "Out of resources";
        case CL_OUT_OF_HOST_MEMORY : return "Out of host memory";
        case CL_PROFILING_INFO_NOT_AVAILABLE : return "Profiling info not available";
        case CL_MEM_COPY_OVERLAP : return "Mem copy overlap";
        case CL_IMAGE_FORMAT_MISMATCH : return "Image format mismatch";
        case CL_IMAGE_FORMAT_NOT_SUPPORTED : return "Image format not supported";
        case CL_BUILD_PROGRAM_FAILURE : return "Build program failure";
        case CL_MAP_FAILURE : return "Map failure";
        case CL_MISALIGNED_SUB_BUFFER_OFFSET : return "Misaligned sub buffer offset";
        case CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST : return "Exec status error for events in wait list";

        case CL_INVALID_VALUE : return "Invalid value";
        case CL_INVALID_DEVICE_TYPE : return "Invalid device type";
        case CL_INVALID_PLATFORM : return "Invalid platform";
        case CL_INVALID_DEVICE : return "Invalid device";
        case CL_INVALID_CONTEXT : return "Invalid context";
        case CL_INVALID_QUEUE_PROPERTIES : return "Invalid queue properties";
        case CL_INVALID_COMMAND_QUEUE : return "Invalid command queue";
        case CL_INVALID_HOST_PTR : return "Invalid host ptr";
        case CL_INVALID_MEM_OBJECT : return "Invalid mem object";
        case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR : return "Invalid image format descriptor";
        case CL_INVALID_IMAGE_SIZE : return "Invalid image size";
        case CL_INVALID_SAMPLER : return "Invalid sampler";
        case CL_INVALID_BINARY : return "Invalid binary";
        case CL_INVALID_BUILD_OPTIONS : return "Invalid build options";
        case CL_INVALID_PROGRAM : return "Invalid program";
        case CL_INVALID_PROGRAM_EXECUTABLE : return "Invalid program executable";
        case CL_INVALID_KERNEL_NAME : return "Invalid kernel name";
        case CL_INVALID_KERNEL_DEFINITION : return "Invalid kernel definition";
        case CL_INVALID_KERNEL : return "Invalid kernel";
        case CL_INVALID_ARG_INDEX : return "Invalid argument index";
        case CL_INVALID_ARG_VALUE : return "Invalid argument value";
        case CL_INVALID_ARG_SIZE : return "Invalid argument size";
        case CL_INVALID_KERNEL_ARGS : return "Invalid kernel arguments";
        case CL_INVALID_WORK_DIMENSION : return "Invalid work dimension";
        case CL_INVALID_WORK_GROUP_SIZE : return "Invalid work group size";
        case CL_INVALID_WORK_ITEM_SIZE : return "Invalid work item size";
        case CL_INVALID_GLOBAL_OFFSET : return "Invalid global offset";
        case CL_INVALID_EVENT_WAIT_LIST : return "Invalid event wait list";
        case CL_INVALID_EVENT : return "Invalid event";
        case CL_INVALID_OPERATION : return "Invalid operation";
        case CL_INVALID_GL_OBJECT : return "Invalid OpenGL object";
        case CL_INVALID_BUFFER_SIZE : return "Invalid buffer size";
        case CL_INVALID_MIP_LEVEL : return "Invalid mip level";
        case CL_INVALID_GLOBAL_WORK_SIZE : return "Invalid global work size";
        case CL_INVALID_PROPERTY : return "Invalid property";
        default : return FormatString() << "Unknown error code=" << code;
    }
}

void checkError(cl_int result, const char* file, int line) {
    if (result == CL_SUCCESS) return;
    throw Exception(FormatString() << errorCodeToMessage(result) << " at " << string(file) << " line " << line);
}

cl_program buildProgram(cl_context context, cl_device_id deviceId, const char* source) {
	cl_int errorCode;    
	
    cl_program program = clCreateProgramWithSource(context, 1, &source, NULL, &errorCode);
    CL_CHECK(errorCode);
	
    errorCode = clBuildProgram(program, 1, &deviceId, NULL, NULL, NULL);
    if (errorCode == CL_BUILD_PROGRAM_FAILURE) {
        size_t size;
        CL_CHECK(clGetProgramBuildInfo(program, deviceId, CL_PROGRAM_BUILD_LOG, 0, NULL, &size));
        
        vector<char> log(size);        
        CL_CHECK(clGetProgramBuildInfo(program, deviceId, CL_PROGRAM_BUILD_LOG, size, &log[0], NULL));
        throw Exception("Build program error: " + string(&log[0]));
    }
    CL_CHECK(errorCode);
    
    return program;
}

cl_program buildProgramFromFile(cl_context context, cl_device_id deviceId, const char* fileName) {	
	
	ifstream file;
	file.exceptions(ios::failbit);
	file.open(fileName);
	string source;
	
	file.seekg(0, ios::end);   
		
	source.reserve(file.tellg());
	file.seekg(0, ios::beg);
	source.assign(istreambuf_iterator<char>(file), istreambuf_iterator<char>());
		
	return buildProgram(context, deviceId, source.c_str());
}

cl_context getContext(cl_command_queue q) {
    cl_context res;
    CL_CHECK(clGetCommandQueueInfo(q, CL_QUEUE_CONTEXT, sizeof(res), &res, NULL));
    return res;
}

cl_device_id getDeviceId(cl_command_queue q) {
	cl_device_id res;
	CL_CHECK(clGetCommandQueueInfo(q, CL_QUEUE_DEVICE, sizeof(res), &res, NULL));
	return res;
}

cl_command_queue createQueue(cl_device_id deviceId, cl_command_queue_properties flags) {
    cl_int errorCode;
    cl_context context = clCreateContext(0, 1, &deviceId, NULL, NULL, &errorCode);
    CL_CHECK(errorCode);

    cl_command_queue queue = clCreateCommandQueue(context, deviceId, flags, &errorCode);
    CL_CHECK(errorCode);
    return queue;
}

void enumOpenCLDevices(vector<cl_device_id> &output, cl_device_type deviceType) {
	cl_uint numPlatforms;
    CL_CHECK(clGetPlatformIDs(0, NULL, &numPlatforms));
	if (numPlatforms == 0) return;
    
    vector<cl_platform_id> platformIds(numPlatforms);
    CL_CHECK(clGetPlatformIDs(numPlatforms, &platformIds[0], NULL));

    for (cl_int i = 0; i < numPlatforms; i++) {
			
        cl_uint numDevices;
		cl_int ret;
		ret = clGetDeviceIDs(platformIds[i], deviceType, 0, NULL, &numDevices);
		if (ret == CL_DEVICE_NOT_FOUND || numDevices == 0) continue;
		CL_CHECK(ret);

        vector<cl_device_id> devices(numDevices);
        CL_CHECK(clGetDeviceIDs(platformIds[i], deviceType, numDevices, &devices[0], NULL));
		
		for (cl_int devIndex = 0; devIndex < numDevices; devIndex++) {			
			output.push_back(devices[devIndex]);
		}
    }
}

